Introduction

假设我们需要实现一种类似google的搜索提示功能,即用户输入搜索词的过程中,输入框的下方会同步显示搜索的候选项:

基本方案:通过input的onChange listener监听input field文本的变化,并通过调用api返回提示结果

存在问题:每次用户输入或删除一个字符,即调用一次API,造成资源浪费。我们希望可以设置一个timer,当用户停止输入一段时间后,才调用api并返回结果

Solution

workflow.png
workflow.png
  1. 设置两个state,分别为text和debouncedText

  2. 每当event listener监听到变化,调用setText函数重设state中的text

  3. 使用useEffect监听text的变化,并执行以下步骤

    1. 设定一个timer,延迟一段时间(500ms)后使用text和setDebouncedText函数来设置debouncedText的值
    2. 返回一个useEffect cleanup funtion来删除这个timer。这个cleanup function将会在下一次text发生变化时调用并清理掉上一次设置的timer。

    假设当前text和debouncedText字符串为”abc”,用户分别再输入’d’和’e’,此时存在两种情况:

    1. 当用户两次键入字符时间小于500ms时,前一次timer的设定时间还没到而不会产生更新,而后面cleanup funtion会清理掉前面设置的timer,因此debouncedText的值不变,仍为”abc”
    2. 当用户两次键入字符时间大于500ms时,前一次timer已到时,会调用setDebounced来更新debouncedText的值,此时为”abcd”
  4. 使用另一个useEffect监听debouncedText的变化,并完成api等操作

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// DebouncedTextField.js
import React, { useState, useEffect } from "react";

const DebouncedTextField = ({ initText, onTextChange }) => {
const [text, setText] = useState(initText);
const [debouncedText, setDebouncedText] = useState(initText);

useEffect(() => {
const timerID = setTimeout(() => {
setDebouncedText(text);
}, 500);

return () => {
clearTimeout(timerID);
};
}, [text]);

// debouncedText发生变化后调用onTextChange执行后续api操作等
useEffect(() => onTextChange(debouncedText), [debouncedText, onTextChange]);

return (
<input type="text" onChange={(e) => setText(e.target.value)} value={text} />
);
};

export default DebouncedTextField;